Popular Searches
Popular Course Categories
Popular Courses

Understanding the Java Thread Life Cycle

Understanding the Java Thread Life Cycle

Multithreading and Concurrency Fundamentals

Understanding the Java Thread Life Cycle

Course: Core Java Training

Topic: Understanding the Java Thread Life Cycle

Course Link: Core Java Training Course

Demo Link: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to the Java Thread Life Cycle

Java supports multithreading, which allows multiple threads of execution to make progress within a program. A thread is a lightweight unit of execution managed by the Java Virtual Machine (JVM). Threads are commonly used for background tasks, file processing, network communication, concurrent calculations, and responsive applications.

The Java thread life cycle describes the different states a thread can occupy from the moment it is created until it finishes execution. Understanding these states helps developers coordinate threads, handle waiting and synchronization, diagnose concurrency problems, and write reliable multithreaded programs.

Java defines six official thread states in the Thread.State enum: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, and TERMINATED.

2. Learning Objectives

  • Understand the meaning of a thread and multithreading in Java.
  • Identify the six official Java thread states.
  • Learn how a thread moves from creation to termination.
  • Understand the roles of start(), run(), sleep(), wait(), and join().
  • Differentiate between BLOCKED, WAITING, and TIMED_WAITING.
  • Observe thread states using getState().
  • Understand synchronization, thread scheduling, and common thread-related mistakes.

3. What Is a Thread in Java?

A thread is an independent path of execution within a Java program. Every Java application normally begins with a main thread, which executes the main() method. Additional threads can be created to perform tasks concurrently.

For example, an application may use separate threads to download a file, process user input, and update application data. The exact order in which threads execute depends on scheduling and program coordination.

Example: Creating a Thread

class MyTask extends Thread {
    @Override
    public void run() {
        System.out.println("Task is running");
    }
}

public class Main {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        task.start();
    }
}

Possible output:

Task is running

The start() method schedules the thread to execute independently. The JVM then invokes its run() method. The output may appear quickly, but the important distinction is that calling start() begins a new thread of execution.

4. Overview of the Six Thread States

State Description Typical Cause
NEW The thread object has been created but has not started. Creating a Thread object.
RUNNABLE The thread is eligible to run or is executing in the JVM. Calling start() or becoming eligible to continue after waiting.
BLOCKED The thread is waiting to acquire a monitor lock. Entering a synchronized method or block when another thread owns the required monitor.
WAITING The thread waits indefinitely for another thread to perform an action. wait(), join() without a timeout, or LockSupport.park().
TIMED_WAITING The thread waits for a specified period. sleep(), timed wait(), or timed join().
TERMINATED The thread has finished execution. The run() method completes or exits abruptly.

5. Understanding Each Stage of the Thread Life Cycle

5.1 NEW State

A thread enters the NEW state when its object has been created but its start() method has not been called.

Thread worker = new Thread(() -> {
    System.out.println("Worker is executing");
});

System.out.println(worker.getState());

Output:

NEW

At this point, the thread has not started executing its task. Calling start() moves it toward the runnable state.

5.2 RUNNABLE State

After start() is called, a thread enters the RUNNABLE state. In Java's official state model, this includes a thread that is ready to run as well as one that is actively executing.

Thread worker = new Thread(() -> {
    System.out.println("Executing task");
});

worker.start();

The scheduler decides when the thread gets execution time. A runnable thread is not guaranteed to execute immediately, and the order of execution among multiple threads is not guaranteed unless the program establishes appropriate coordination.

Important: Java does not define a separate official RUNNING state in Thread.State. Some educational diagrams use “running” as a conceptual stage, but the official Java state is RUNNABLE.

5.3 BLOCKED State

A thread enters BLOCKED when it is waiting to acquire a Java monitor lock so that it can enter or re-enter a synchronized block or method. This can occur when another thread already owns that monitor.

class SharedResource {
    synchronized void useResource() {
        System.out.println(
            Thread.currentThread().getName() + " acquired the lock"
        );

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        Thread first = new Thread(resource::useResource, "First");
        Thread second = new Thread(resource::useResource, "Second");

        first.start();
        second.start();
    }
}

Possible output:

First acquired the lock
Second acquired the lock

The exact output order is not guaranteed. While one thread owns the monitor, the other may enter the BLOCKED state while trying to enter the synchronized method. The first thread's call to sleep() does not release the monitor, so the second thread cannot enter that method until the lock becomes available.

5.4 WAITING State

A thread enters WAITING when it waits indefinitely for another thread to perform a particular action. Common causes include calling Object.wait() without a timeout, Thread.join() without a timeout, and LockSupport.park().

When a thread calls wait() on an object, it releases that object's monitor and waits until it is notified or otherwise resumes according to the method's rules. After notification, it must reacquire the monitor before continuing.

class Message {
    synchronized void waitForMessage() {
        try {
            wait();
            System.out.println("Notification received");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    synchronized void sendNotification() {
        notifyAll();
    }
}

One thread can call waitForMessage() while another calls sendNotification(). The waiting thread can continue only after it is notified and reacquires the object's monitor.

Important: Calling wait() requires owning the corresponding object's monitor, normally by executing inside a synchronized method or block. Otherwise, Java throws IllegalMonitorStateException.

5.5 TIMED_WAITING State

A thread enters TIMED_WAITING when it waits for a specified duration. The thread can resume when the time expires, when it is notified where applicable, or when it is interrupted, depending on the operation.

Common examples include:

  • Thread.sleep(milliseconds)
  • Object.wait(milliseconds) with a positive timeout
  • Thread.join(milliseconds) with a positive timeout
  • LockSupport.parkNanos()
  • LockSupport.parkUntil()
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("Worker resumed");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        worker.start();
        Thread.sleep(100);
        System.out.println(worker.getState());
    }
}

Typical output:

TIMED_WAITING

The sample uses a short delay to make it more likely that the worker is sleeping when its state is checked. Because thread scheduling is nondeterministic, the observed state is not guaranteed in every run.

5.6 TERMINATED State

A thread enters TERMINATED after its execution completes. This happens when the run() method returns normally or exits abruptly, such as because of an uncaught exception.

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("Task completed");
        });

        worker.start();
        worker.join();

        System.out.println(worker.getState());
    }
}

Output:

Task completed
TERMINATED

The join() call makes the main thread wait until the worker terminates, so the final state check occurs after the task has finished. A terminated thread cannot be started again; attempting to call start() a second time throws IllegalThreadStateException.

6. Thread Life Cycle Flow

A simplified conceptual flow is:

NEW
 |
 | start()
 v
RUNNABLE <------------------------------+
 |                                      |
 | attempts to acquire a busy monitor   | lock acquired
 v                                      |
BLOCKED --------------------------------+
 |
 | waits for a notification or completion
 v
WAITING
 |
 | condition satisfied; lock reacquired if required
 v
RUNNABLE

RUNNABLE -- timed sleep/wait/join --> TIMED_WAITING
TIMED_WAITING -- timeout or notification --> RUNNABLE
RUNNABLE -- run() completes --> TERMINATED

This is a conceptual overview, not a complete state-transition specification. A thread can move repeatedly between runnable and waiting-related states during its life. For example, a thread can become blocked on a monitor, later return to runnable, and eventually terminate.

7. Important Thread Methods and Their Effects

Method Purpose Life-Cycle Effect
start() Schedules a thread to begin execution. Moves a new thread toward RUNNABLE.
run() Contains or invokes the task's execution logic. Calling it directly does not start a new thread.
sleep() Pauses the current thread for a specified time. Normally causes TIMED_WAITING.
wait() Waits for a notification or a timeout on an object's monitor. Causes WAITING or TIMED_WAITING.
notify() Notifies one thread waiting on the object's monitor. The notified thread may later become runnable after reacquiring the monitor.
notifyAll() Notifies all threads waiting on the object's monitor. Notified threads compete to reacquire the monitor.
join() Waits for another thread to finish. The calling thread may enter WAITING or TIMED_WAITING.
interrupt() Requests interruption of a thread. May cause interruptible waiting methods to throw InterruptedException.
getState() Returns the thread's current JVM state. Observes the state; it does not change it.
isAlive() Checks whether a thread has started and has not yet terminated. Returns a boolean rather than a state value.

8. Difference Between start() and run()

The distinction between start() and run() is one of the most important concepts in Java multithreading.

public class Main {
    public static void main(String[] args) {
        Thread worker = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
        });

        worker.run();   // Executes on the current thread
        worker.start(); // Starts a separate thread
    }
}

Possible output:

main
Thread-0

The thread name generated by Java can vary depending on how the thread is constructed. Calling run() directly behaves like an ordinary method call, so the task executes on the calling thread. Calling start() schedules the new thread to execute the task independently.

9. Monitoring Thread States with getState()

The getState() method returns a value from the Thread.State enum. It can be useful for learning, debugging, and diagnostic logging, but the result is only a snapshot. A thread can change state immediately after the method returns.

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        System.out.println("Before start: " + worker.getState());

        worker.start();
        Thread.sleep(100);
        System.out.println("After start: " + worker.getState());

        worker.join();
        System.out.println("After completion: " + worker.getState());
    }
}

Typical output:

Before start: NEW
After start: TIMED_WAITING
After completion: TERMINATED

The second line may instead show another state if the worker has not yet started sleeping when the state is checked. Avoid using repeated getState() checks as the main mechanism for coordinating threads. Prefer synchronization tools such as locks, latches, blocking queues, and futures.

10. Thread Synchronization and the Life Cycle

Synchronization coordinates access to shared resources. A synchronized method or block uses an object's monitor to ensure that only one thread at a time owns that monitor.

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread first = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread second = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        first.start();
        second.start();

        first.join();
        second.join();

        System.out.println(counter.getCount());
    }
}

Output:

2000

The synchronized methods protect updates to the shared counter. The join() calls ensure both worker threads finish before the final value is printed. Without suitable synchronization, concurrent updates to shared mutable data can produce incorrect results.

11. Common Thread Life-Cycle Mistakes

Mistake Why It Is a Problem Correct Practice
Calling run() instead of start() The task runs on the calling thread rather than a new thread. Call start() to begin a separate thread of execution.
Starting the same thread twice A thread can be started only once. Create a new thread object for a new execution.
Assuming a fixed scheduling order The scheduler does not guarantee arbitrary thread ordering. Use synchronization and coordination mechanisms.
Assuming sleep() releases a monitor The sleeping thread retains any monitor locks it owns. Use appropriate coordination; wait() releases the monitor on which it is called.
Using wait() without owning the monitor It causes IllegalMonitorStateException. Call it while holding the corresponding object's monitor.
Ignoring interruption Cancellation and shutdown requests may not be handled properly. Handle InterruptedException and restore the interrupt status when appropriate.
Using getState() for synchronization The state can change immediately after inspection. Use latches, locks, futures, or other coordination primitives.

12. Best Practices for Managing Threads

  • Prefer implementing Runnable or using task abstractions when separating task logic from thread management.
  • Use an executor service for managing groups of tasks rather than creating large numbers of platform threads manually.
  • Protect shared mutable data with appropriate synchronization or concurrency utilities.
  • Use join() when a thread must wait for another thread to complete.
  • Respect interruption as a cooperative cancellation mechanism.
  • Avoid deprecated thread-control methods such as stop(), suspend(), and resume().
  • Use descriptive thread names to make logs and debugging easier.
  • Remember that BLOCKED specifically means waiting for a monitor lock, not every kind of resource wait.
  • Use thread dumps and appropriate monitoring tools when diagnosing deadlocks or stalled tasks.

13. Practice Exercises

  1. Create a thread and display its state before calling start().
  2. Write a program that starts a thread, waits for it using join(), and prints its final state.
  3. Demonstrate the difference between calling run() and start().
  4. Create two threads that use a synchronized method to update a shared counter.
  5. Write a program that demonstrates TIMED_WAITING using Thread.sleep().
  6. Explain how wait() differs from sleep() in terms of monitor ownership.
  7. Investigate why a thread may be in BLOCKED while attempting to enter a synchronized method.

14. Interview Questions and Answers

Q1. What is the Java thread life cycle?

Answer: It is the sequence of states a thread may occupy from creation until completion. Java defines six official states: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, and TERMINATED.

Q2. What is the NEW state?

Answer: A thread is in the NEW state after its object is created but before its start() method is called.

Q3. What is the difference between RUNNABLE and RUNNING?

Answer: Java's official state model uses RUNNABLE to cover a thread that is ready to execute or executing. RUNNING is commonly used informally to describe active execution, but it is not a separate value in Thread.State.

Q4. When does a thread enter the BLOCKED state?

Answer: A thread enters BLOCKED when it waits to acquire a monitor lock needed to enter or re-enter a synchronized method or block.

Q5. What is the difference between WAITING and TIMED_WAITING?

Answer: WAITING is an indefinite wait for another thread's action, while TIMED_WAITING is a wait with a specified time limit.

Q6. Does sleep() release a lock?

Answer: No. Thread.sleep() pauses the current thread but does not release monitor locks that the thread holds.

Q7. Does wait() release a lock?

Answer: Calling wait() on an object releases that object's monitor while the thread waits. Before continuing, the thread must reacquire that monitor.

Q8. Can a terminated thread be restarted?

Answer: No. A thread object can be started only once. A new thread object is needed for a new execution.

Q9. What is the purpose of join()?

Answer: join() lets one thread wait for another thread to terminate. It is useful when a task must finish before the program continues with a dependent operation.

Q10. How can the current state of a thread be checked?

Answer: Use getState(), which returns a Thread.State value. The returned state is only a snapshot and should not be used as a substitute for proper synchronization.

15. Quick Revision Checklist

  • NEW: the thread is created but has not started.
  • RUNNABLE: the thread is ready to run or executing.
  • BLOCKED: the thread is waiting to acquire a monitor lock.
  • WAITING: the thread waits indefinitely for an action.
  • TIMED_WAITING: the thread waits for a specified duration.
  • TERMINATED: the thread's execution has ended.
  • start() schedules a separate thread of execution; direct run() invocation does not.
  • sleep() does not release monitor locks.
  • wait() releases the monitor on which it is called.
  • A thread object cannot be started a second time.

16. Conclusion

The Java thread life cycle is fundamental to understanding multithreading and concurrency. By learning the six official states and the methods that influence them, developers can better understand scheduling, waiting, synchronization, and thread termination.

Practice creating threads, checking their states, coordinating execution with join(), and protecting shared resources with synchronization. These skills provide a strong foundation for more advanced topics such as executor services, concurrent collections, locks, futures, and parallel processing.

Continue learning with the Core Java Training Course, register for a course demo, or visit the JustAcademy website.

Previous Next
whatsapp